<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgpu - point lights</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="example.css">
	</head>
	<body>

		<div id="info">
			<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

			<div class="title-wrapper">
				<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Point Lights</span>
			</div>

			<small>
				Walt Disney head by <a href="http://web.archive.org/web/20120903131400/http://davidoreilly.com/post/18087489343/disneyhead" target="_blank" rel="noopener">David OReilly</a><br />
				Displacement effect by <a href="https://oosmoxiecode.com/archive/js_webgl/stanford_bunny/" target="_blank" rel="noopener">oosmoxiecode</a>
			</small>
		</div>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.webgpu.js",
					"three/webgpu": "../build/three.webgpu.js",
					"three/tsl": "../build/three.tsl.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three/webgpu';

			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

			import { abs, attribute, distance, float, max, modelWorldMatrixInverse, positionLocal, sin, time, uniform } from 'three/tsl';

			let camera, scene, timer, renderer, controls;

			let light1, light2;

			init();

			function init() {

				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.z = 100;

				scene = new THREE.Scene();

				timer = new THREE.Timer();
				timer.connect( document );

				// model

				const loader = new OBJLoader();
				loader.load( 'models/obj/walt/WaltHead.obj', function ( obj ) {

					const mesh = obj.children[ 0 ];
					mesh.geometry = createGeometry( mesh.geometry );
					mesh.material = createMaterial();

					mesh.scale.multiplyScalar( 0.8 );
					mesh.position.y = - 30;
					scene.add( mesh );

				} );

				const sphere = new THREE.SphereGeometry( 0.5, 16, 8 );

				// lights

				light1 = new THREE.PointLight( 0xff0040, 2000 );
				light1.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0xff0040 } ) ) );
				scene.add( light1 );

				light2 = new THREE.PointLight( 0x0040ff, 2000 );
				light2.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0x0040ff } ) ) );
				scene.add( light2 );

				scene.add( new THREE.AmbientLight( 0xaaaaaa, 0.1 ) );

				// renderer

				renderer = new THREE.WebGPURenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setAnimationLoop( animate );
				document.body.appendChild( renderer.domElement );

				controls = new OrbitControls( camera, renderer.domElement );
				controls.enableDamping = true;

				window.addEventListener( 'resize', onWindowResize );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function animate() {

				timer.update();

				const time = timer.getElapsed() * 0.5;

				controls.update();

				light1.position.x = Math.sin( time ) * 20;
				light1.position.y = Math.cos( time * 0.75 ) * - 30;
				light1.position.z = Math.cos( time * 0.5 ) * 20;

				light2.position.x = Math.cos( time * 0.5 ) * 20;
				light2.position.y = Math.sin( time * 0.75 ) * - 30;
				light2.position.z = Math.sin( time ) * 20;

				renderer.render( scene, camera );

			}

			// helpers

			function createMaterial() {

				const material = new THREE.MeshPhongNodeMaterial();

				const seedAttribute = attribute( 'seed' );
				const displaceNormalAttribute = attribute( 'displaceNormal' );

				const localTime = attribute( 'time' ).add( time );

				const effector1 = uniform( light1.position ).toVar();
				const effector2 = uniform( light2.position ).toVar();

				const distance1 = distance( positionLocal, modelWorldMatrixInverse.mul( effector1 ) );
				const distance2 = distance( positionLocal, modelWorldMatrixInverse.mul( effector2 ) );

				const invDistance1 = max( 0.0, float( 20.0 ).sub( distance1 ) ).div( 2.0 );
				const invDistance2 = max( 0.0, float( 20.0 ).sub( distance2 ) ).div( 2.0 );

				const s = abs( sin( localTime.mul( 2 ).add( seedAttribute ) ).mul( 0.5 ) ).add( invDistance1 ).add( invDistance2 );

				material.positionNode = positionLocal.add( displaceNormalAttribute.mul( s ) );

				return material;

			}

			function createGeometry( geometry ) {

				const positionAttribute = geometry.getAttribute( 'position' );

				const v0 = new THREE.Vector3();
				const v1 = new THREE.Vector3();
				const v2 = new THREE.Vector3();
				const v3 = new THREE.Vector3();
				const n = new THREE.Vector3();

				const plane = new THREE.Plane();

				const vertices = [];
				const times = [];
				const seeds = [];
				const displaceNormal = [];

				for ( let i = 0; i < positionAttribute.count; i += 3 ) {

					v0.fromBufferAttribute( positionAttribute, i );
					v1.fromBufferAttribute( positionAttribute, i + 1 );
					v2.fromBufferAttribute( positionAttribute, i + 2 );

					plane.setFromCoplanarPoints( v0, v1, v2 );

					v3.copy( v0 ).add( v1 ).add( v2 ).divideScalar( 3 ); // compute center
					v3.add( n.copy( plane.normal ).multiplyScalar( - 1 ) ); // displace center inwards

					// generate tetrahedron for each triangle

					vertices.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z );
					vertices.push( v3.x, v3.y, v3.z, v1.x, v1.y, v1.z, v0.x, v0.y, v0.z );
					vertices.push( v3.x, v3.y, v3.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z );
					vertices.push( v3.x, v3.y, v3.z, v0.x, v0.y, v0.z, v2.x, v2.y, v2.z );

					const t = Math.random();
					const s = Math.random();
					n.copy( plane.normal );

					times.push( t, t, t ); times.push( t, t, t ); times.push( t, t, t ); times.push( t, t, t );
					seeds.push( s, s, s ); seeds.push( s, s, s ); seeds.push( s, s, s ); seeds.push( s, s, s );

					displaceNormal.push( n.x, n.y, n.z, n.x, n.y, n.z, n.x, n.y, n.z );
					displaceNormal.push( n.x, n.y, n.z, n.x, n.y, n.z, n.x, n.y, n.z );
					displaceNormal.push( n.x, n.y, n.z, n.x, n.y, n.z, n.x, n.y, n.z );
					displaceNormal.push( n.x, n.y, n.z, n.x, n.y, n.z, n.x, n.y, n.z );

				}

				const newGeometry = new THREE.BufferGeometry();
				newGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
				newGeometry.setAttribute( 'time', new THREE.Float32BufferAttribute( times, 1 ) );
				newGeometry.setAttribute( 'seed', new THREE.Float32BufferAttribute( seeds, 1 ) );
				newGeometry.setAttribute( 'displaceNormal', new THREE.Float32BufferAttribute( displaceNormal, 3 ) );

				newGeometry.computeVertexNormals();

				return newGeometry;


			}

		</script>
	</body>
</html>
